﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using com.yoozoo.gta.Extension;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Profiling;
using XLua;
using Yoozoo.Framework.Managers;
using Object = UnityEngine.Object;

namespace Yoozoo.Manager
{
    [DefaultExecutionOrder(-32000)]
    public class FrameManager : MonoBehaviour
    {
        private static GameObject s_Go;
        private static FrameManager s_Instance;

#if UNITY_EDITOR
        [BlackList]
        public static void Initialize()
        {
            s_Go = new GameObject("FrameManager");
            s_Instance = s_Go.AddComponent<FrameManager>();
            SetNode2Manager();
        }
#else
        static FrameManager()
        {
            s_Go = new GameObject("FrameManager");
            s_Instance = s_Go.AddComponent<FrameManager>();
            SetNode2Manager();
        }
#endif

        static void SetNode2Manager()
        {
            GameObject manager = GameObject.Find("ManagerObj");
            if (manager == null)
            {
                manager = new GameObject("ManagerObj");
                DontDestroyOnLoad(manager);
            }
            s_Go.transform.SetParent(manager.transform, false);
        }
        
        private static void FrameUpdateGet(FrameUpdate element)
        {
            if (m_root == null)
            {
                m_root = element;
                s_Instance.enabled = true;
            }
            else
            {
                m_tail.Next = element;
            }
            m_tail = element;

            element.IsAlive = true;
        }
        private static void FrameUpdateRelease(FrameUpdate element)
        {
            if (m_root == element)
            {
                m_root = element.Next;
                if (m_tail == element)
                    m_tail = m_root;
                if (m_root == null)
                    s_Instance.enabled = false;
            }
            else
            {
                var node = m_root;
                while (node.Next != null)
                {
                    if (node.Next == element)
                    {
                        node.Next = element.Next;
                        if (m_tail == element)
                            m_tail = node.Next != null ? node.Next : node;
                        break;
                    }
                
                    node = node.Next;
                }
            }
            
            element.OnDispose();
        }
        private static void FrameGet(Frame element)
        {
            
        }
        private static void FrameRelease(Frame element)
        {
            element.WaitUntil = null;
        }
        
        public static ObjectPool<FrameUpdate> m_pool = new ObjectPool<FrameUpdate>(FrameUpdateGet, FrameUpdateRelease);
        public static ObjectPool<Frame> m_framePool = new ObjectPool<Frame>(FrameGet, FrameRelease);
        private static FrameUpdate m_root;
        private static FrameUpdate m_tail;
        private static FrameUpdate m_current;
        private static FrameUpdate m_next;
        
        private static float m_FrameTime;
        private static int m_FrameCount;
        //private static Stopwatch m_stopWatch = new Stopwatch(); 

        public static FrameUpdate Get(IEnumerator enumerator, Object obj = null, string frameName = null)
        {
            var current = m_pool.Get();
            current.Enumerator = enumerator;
            current.Obj = obj;
            current.m_frameName = frameName;
            current.OnUpdate(Time.time, Time.frameCount);
            return current;
        }
        
        void Update()
        {
            //m_stopWatch.Restart();
            m_FrameTime = Time.time;
            m_FrameCount = Time.frameCount;
            m_current = m_root;
            while (m_current != null)
            {
               
                m_next = m_current.Next;
                var frameName = string.IsNullOrEmpty(m_current.m_frameName) ? "UnkonwFrameName" : m_current.m_frameName  ;
                Profiler.BeginSample(frameName);
                m_current.OnUpdate(m_FrameTime, m_FrameCount);
                Profiler.EndSample();
                m_current = m_next;
                // if (m_stopWatch.ElapsedMilliseconds >= 20)
                // {
                //     //UnityEngine.Debug.LogWarning($"耗时:{m_stopWatch.ElapsedMilliseconds}ms");
                //     break;
                // }
            }
            //m_stopWatch.Stop();
        }
    }

    /// <summary>
    /// 帧类型
    /// </summary>
    public enum FrameType
    {
        /// <summary>
        /// 等待一帧继续执行
        /// </summary>
        Frame = 1,
        /// <summary>
        /// 等待指定秒数继续执行
        /// </summary>
        WaitForSeconds = 2,
        /// <summary>
        /// 等待直到返回true再继续执行
        /// </summary>
        WaitUntil = 3,
    }
    
    public class Frame
    {
        public FrameType FrameType { get; set; }
        public string FrameName { get; set; }
        public float EndTime { get; set; }
        public float WaitForSeconds { get; set; }
        public WaitUntil WaitUntil { get; set; }
    }
    
    public class FrameUpdate 
    {
        private readonly ObjectPool<Frame> m_framePool = FrameManager.m_framePool;
        private Frame m_frame;
        public string m_frameName;
        /// <summary>
        /// 上次执行OnUpdate的帧数
        /// </summary>
        public int m_LastExcuteFrameCount;
        public Object Obj { get; set; }
        /// <summary>
        /// 当前类协程是否存活
        /// </summary>
        public bool IsAlive { get; set; }
        public IEnumerator Enumerator { get; set; }
        public FrameUpdate Next { get; set; }

        public void OnUpdate(float frameTime, int frameCount)
        {
            if (m_LastExcuteFrameCount == frameCount)
            {
                return;
            }
            m_LastExcuteFrameCount = frameCount;
            
            if (Obj != null && Obj.Invalid())
            {
                this.IsAlive = false;
                this.Dispose();
                return;
            }
            
            if (Enumerator != null && m_frame == null)
            {
                bool moveNext = false;
                try
                {
                    moveNext = Enumerator.MoveNext();
                }
                catch (Exception e)
                {
                    UnityEngine.Debug.LogErrorFormat("FrameName: {0}  errMsg:{1}",this.m_frameName,e.Message);
                }
                if (moveNext)
                {
                    var current = Enumerator.Current;
                    
                    m_frame = m_framePool.Get();
                    if (current == null)
                    {
                        m_frame.FrameType = FrameType.Frame;
                    }
                    else if (current is WaitForSeconds)
                    {
                        var fieldInfo = current.GetType().GetField("m_Seconds", BindingFlags.NonPublic | BindingFlags.Instance);
                        float waitForSeconds = (float)fieldInfo.GetValue(current);
                        
                        m_frame.FrameType = FrameType.WaitForSeconds;
                        m_frame.WaitForSeconds = waitForSeconds;
                        m_frame.EndTime = frameTime + m_frame.WaitForSeconds;
                    }
                    else if (current is WaitUntil)
                    {
                        m_frame.FrameType = FrameType.WaitUntil;
                        m_frame.WaitUntil = current as WaitUntil;
                    }
                }
                else
                {
                    this.IsAlive = false;
                    this.Dispose();
                }
            }
            
            if (m_frame != null)
            {
                if (m_frame.FrameType == FrameType.Frame)
                {
                    m_framePool.Release(m_frame);
                    m_frame = null;
                }
                else if (m_frame.FrameType == FrameType.WaitForSeconds)
                {
                    if (frameTime >= m_frame.EndTime)
                    {
                        m_framePool.Release(m_frame);
                        m_frame = null;
                    }
                }
                else if (m_frame.FrameType == FrameType.WaitUntil)
                {
                    if (!m_frame.WaitUntil.keepWaiting)
                    {
                        m_framePool.Release(m_frame);
                        m_frame = null;
                    }
                }
                else
                {
                    m_framePool.Release(m_frame);
                    m_frame = null;
                }
            }
        }

        public void Dispose()
        {
            FrameManager.m_pool.Release(this);
        }

        public void OnDispose()
        {
            if (m_frame != null)
            {
                m_framePool.Release(m_frame);
                m_frame = null;
            }

            m_frameName = null;
            Enumerator = null;
            Obj = null;
            Next = null;
        }
    }
    
    public class ObjectPool<T> where T : new()
    {
        readonly Stack<T> m_Stack = new Stack<T>();
        readonly UnityAction<T> m_ActionOnGet;
        readonly UnityAction<T> m_ActionOnRelease;
        readonly Func<T> m_ActionOnAlloc;
        readonly bool m_CollectionCheck = true;

        /// <summary>
        /// Number of objects in the pool.
        /// </summary>
        public int countAll { get; private set; }
        /// <summary>
        /// Number of active objects in the pool.
        /// </summary>
        public int countActive { get { return countAll - countInactive; } }
        /// <summary>
        /// Number of inactive objects in the pool.
        /// </summary>
        public int countInactive { get { return m_Stack.Count; } }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="actionOnGet">Action on get.</param>
        /// <param name="actionOnRelease">Action on release.</param>
        /// <param name="collectionCheck">True if collection integrity should be checked.</param>
        public ObjectPool(UnityAction<T> actionOnGet = null, UnityAction<T> actionOnRelease = null, Func<T> actionOnAlloc = null, bool collectionCheck = true)
        {
            m_ActionOnGet = actionOnGet;
            m_ActionOnRelease = actionOnRelease;
            m_ActionOnAlloc = actionOnAlloc;
            m_CollectionCheck = collectionCheck;
        }

        /// <summary>
        /// Get an object from the pool.
        /// </summary>
        /// <returns>A new object from the pool.</returns>
        public T Get()
        {
            T element;
            if (m_Stack.Count == 0)
            {
                element = m_ActionOnAlloc != null ? m_ActionOnAlloc() : new T();
                countAll++;
            }
            else
            {
                element = m_Stack.Pop();
            }
            if (m_ActionOnGet != null)
                m_ActionOnGet(element);
            return element;
        }
        
        /// <summary>
        /// Release an object to the pool.
        /// </summary>
        /// <param name="element">Object to release.</param>
        public void Release(T element)
        {
#if UNITY_EDITOR // keep heavy checks in editor
            if (m_CollectionCheck && m_Stack.Count > 0)
            {
                if (m_Stack.Contains(element))
                    Debug.LogError("Internal error. Trying to destroy object that is already released to pool.");
            }
#endif
            if (m_ActionOnRelease != null)
                m_ActionOnRelease(element);
            m_Stack.Push(element);
        }
    }
}
